package util

import (
	"fmt"
	"unsafe"
)

/*
 结构体

 Go语言中的基础数据类型可以表示一些事物的基本属性，但是当我们想表达一个事物的全部或部分属性时，
 这时候再用单一的基本数据类型明显就无法满足需求了，
 Go语言提供了一种自定义数据类型，可以封装多个基本数据类型，
 这种数据类型叫结构体，英文名称`struct`。 也就是我们可以通过`struct`来定义自己的类型了。

 Go语言中通过struct来实现面向对象。
*/

/*
结构体的定义
	使用type和struct关键字来定义结构体，具体代码格式如下：

	type 类型名 struct {
		字段名 字段类型
		字段名 字段类型
		…
	}
其中：
	类型名：标识自定义结构体的名称，在同一个包内不能重复。
	字段名：表示结构体字段名。结构体中的字段名必须唯一。
	字段类型：表示结构体字段的具体类型。
	举个例子，我们定义一个Person（人）结构体，代码如下：
*/
type Person struct {
	Name, City string
	Age        int
	/*
		或
		name string
		city string
		age  int8
	*/
	/*
		这样我们就拥有了一个person的自定义类型，它有name、city、age三个字段，
		分别表示姓名、城市和年龄。这样我们使用这个person结构体就能够很方便的在程序中表示和存储人信息了。

		语言内置的基础数据类型是用来描述一个值的，而结构体是用来描述一组值的。
		比如一个人有名字、年龄和居住城市等，本质上是一种聚合型的数据类型
	*/
}

/*
结构体实例化
	只有当结构体实例化时，才会真正地分配内存。也就是必须实例化后才能使用结构体的字段。

	结构体本身也是一种类型，我们可以像声明内置类型一样使用var关键字声明结构体类型。
		`var 结构体实例 结构体类型`
*/
func TestStructInstance() {
	var person Person
	person.Name = "张三"
	person.Age = 22
	person.City = "北京"
	fmt.Printf("person: %v\n", person)  //person: {张三 北京 22}
	fmt.Printf("person: %#v\n", person) //person: util.Person{Name:"张三", City:"北京", Age:22}
}

/*
匿名结构体
在定义一些临时数据结构等场景下还可以使用匿名结构体。
*/
func TestAnonymityStruct() {
	var user struct {
		Name, City string
		Age        int
	}
	user.Name = "张三"
	user.Age = 22
	user.City = "北京"
	fmt.Printf("user: %#v\n", user) //user: struct { Name string; City string; Age int }{Name:"张三", City:"北京", Age:22}
}

/*
创建指针类型结构体
我们还可以通过使用new关键字对结构体进行实例化，得到的是结构体的地址。 格式如下：
*/
func TestStructPointer() {
	p := new(Person)
	fmt.Printf("p: %v\n", p)  //p: &{  0}
	fmt.Printf("p: %#v\n", p) //p: &util.Person{Name:"", City:"", Age:0}
	/*
			从打印的结果中我们可以看出p是一个结构体指针。

		    需要注意的是在Go语言中支持对结构体指针直接使用.来访问结构体的成员。
	*/
	p.Name = "张三"
	p.Age = 22
	p.City = "北京"
	fmt.Printf("p: %#v\n", p) //p: &util.Person{Name:"张三", City:"北京", Age:22}
	/*
			取结构体的地址实例化
		    使用&对结构体进行取地址操作相当于对该结构体类型进行了一次new实例化操作。
	*/
	p2 := &Person{}
	fmt.Printf("p2: %v\n", p2)  //p2: &{  0}
	fmt.Printf("p2: %#v\n", p2) //p2: &util.Person{Name:"", City:"", Age:0}
	p2.Name = "张三"
	p2.Age = 22
	p2.City = "北京"
	fmt.Printf("p2: %#v\n", p2) //p2: &util.Person{Name:"张三", City:"北京", Age:22}
	//p2.Name = "张三"其实在底层是(*p2).Name = "张三"，这是Go语言帮我们实现的语法糖。
}

/*
没有初始化的结构体，其成员变量都是对应其类型的默认初始值。
*/
func TestStructInitialize() {
	var p Person
	fmt.Printf("p: %#v\n", p) //p: &util.Person{Name:"", City:"", Age:0}

	//使用键值对对结构体进行初始化时，键对应结构体的字段，值对应该字段的初始值。
	p2 := Person{Name: "张三", City: "北京", Age: 20}
	fmt.Printf("p2: %#v\n", p2) //p2: util.Person{Name:"张三", City:"北京", Age:22}

	//也可以对结构体指针进行键值对初始化，例如：
	p3 := &Person{Name: "张三", City: "北京", Age: 20}
	fmt.Printf("p3: %#v\n", p3) //p2: &util.Person{Name:"张三", City:"北京", Age:22}

	//当某些字段没有初始值的时候，该字段可以不写。此时，没有指定初始值的字段的值就是该字段类型的零值。
	p4 := Person{Name: "张三"}
	fmt.Printf("p4: %#v\n", p4) //p4: util.Person{Name:"张三", City:"", Age:0}

	//使用值的列表初始化 初始化结构体的时候可以简写，也就是初始化的时候不写键，直接写值：
	p5 := &Person{
		"张三",
		"北京",
		20,
	}
	fmt.Printf("p5: %#v\n", p5) //p5: &util.Person{Name:"张三", City:"北京", Age:22}
	/*
		使用这种格式初始化时，需要注意：

			必须初始化结构体的所有字段。
			初始值的填充顺序必须与字段在结构体中的声明顺序一致。
			该方式不能和键值初始化方式混用。
	*/
}

/*
结构体内存布局
结构体占用一块连续的内存。
*/
func TestStructMemoryLayout() {
	p := Person{
		"张三",
		"北京",
		20,
	}
	fmt.Printf("(&p.Name): %p\n", (&p.Name))
	fmt.Printf("(&p.City): %p\n", (&p.City))
	fmt.Printf("(&p.Age): %p\n", (&p.Age))
	fmt.Printf("unsafe.Sizeof(p): %v\n", unsafe.Sizeof(p))
	var null struct{}
	//空结构体是不占用空间的。
	fmt.Printf("unsafe.Sizeof(null): %v\n", unsafe.Sizeof(null))
}
